#include <SDL.h>
#include "wxvbam.h"
#include "../common/SoundSDL.h"
#include <wx/ffile.h>
#include <wx/print.h>
#include <wx/printdlg.h>
#include <wx/generic/prntdlgg.h>

// These should probably be in vbamcore
int systemVerbose;
int systemFrameSkip;

int systemRedShift;
int systemGreenShift;
int systemBlueShift;
int systemColorDepth;
u16 systemColorMap16[0x10000];
u32 systemColorMap32[0x10000];
#define gs555(x) (x | (x << 5) | (x << 10))
u16 systemGbPalette[24] = {
    gs555(0x1f), gs555(0x15), gs555(0x0c), 0,
    gs555(0x1f), gs555(0x15), gs555(0x0c), 0,
    gs555(0x1f), gs555(0x15), gs555(0x0c), 0,
    gs555(0x1f), gs555(0x15), gs555(0x0c), 0,
    gs555(0x1f), gs555(0x15), gs555(0x0c), 0,
    gs555(0x1f), gs555(0x15), gs555(0x0c), 0
};
int RGB_LOW_BITS_MASK;

// these are local, though.
int joypress[4], autofire;
static int sensorx[4], sensory[4];
bool pause_next;
bool turbo;

// and this is from MFC interface
bool soundBufferLow;

void systemMessage(int id, const char * fmt, ...)
{
    static char *buf = NULL;
    static int buflen = 80;
    va_list args;
    // auto-conversion of wxCharBuffer to const char * seems broken
    // so save underlying wxCharBuffer (or create one of none is used)
    wxCharBuffer _fmt(wxString(wxGetTranslation(wxString(fmt, wxConvLibc))).utf8_str());

    if(!buf) {
	buf = (char *)malloc(buflen);
	if(!buf)
	    exit(1);
    }
    while(1) {
	va_start(args, fmt);
	int needsz = vsnprintf(buf, buflen, _fmt.data(), args);
	va_end(args);
	if(needsz < buflen)
	    break;
	while(buflen <= needsz)
	    buflen *= 2;
	free(buf);
	buf = (char *)malloc(buflen);
	if(!buf)
	    exit(1);
    }
    wxLogError(wxT("%s"), wxString(buf, wxConvLibc).c_str());
}

static int frames = 0;

void systemDrawScreen()
{
    frames++;
    MainFrame *mf = wxGetApp().frame;
    mf->UpdateViewers();
    // FIXME: Sm60FPS crap and sondBufferLow crap
    GameArea *ga = mf->GetPanel();
#ifndef NO_FFMPEG
    if(ga)
	ga->AddFrame(pix);
#endif
    if(ga && ga->panel)
	ga->panel->DrawArea(&pix);
}

// record a game "movie"
// actually just game save state combined with a keystroke log
// doesn't work in GB "multiplayer" mode (only records default joypad)
//
//  <name>.vmv = keystroke log; all values little-endian ints:
//     <version>.32 = 1
//     for every joypad change (init to 0) and once at end of movie {
//        <timestamp>.32 = frames since start of movie
//        <joypad>.32 = default joypad reading at that time
//     }
//  <name>.vm0 = saved state

wxFFile game_file;
bool game_recording, game_playback;
u32 game_frame;
u32 game_joypad;

void systemStartGameRecording(const wxString &fname)
{
    GameArea *panel = wxGetApp().frame->GetPanel();
    if(!panel || panel->game_type() == IMAGE_UNKNOWN ||
       !panel->emusys->emuWriteState) {
	wxLogError(_("No game in progress to record"));
	return;
    }
    systemStopGamePlayback();
    wxString fn = fname;
    if(fn.size() < 4 || !wxString(fn.substr(fn.size() - 4)).IsSameAs(wxT(".vmv"), false))
	fn.append(wxT(".vmv"));
    u32 version = 1;
    if(!game_file.Open(fn.c_str(), wxT("wb")) ||
       game_file.Write(&version, sizeof(version)) != sizeof(version)) {
	wxLogError(_("Cannot open output file %s"), fname.c_str());
	return;
    }
    fn[fn.size() - 1] = wxT('0');
    if(!panel->emusys->emuWriteState(fn.mb_fn_str())) {
	wxLogError(_("Error writing game recording"));
	game_file.Close();
	return;
    }
    game_frame = 0;
    game_joypad = 0;
    game_recording = true;
    MainFrame *mf = wxGetApp().frame;
    mf->cmd_enable &= ~(CMDEN_NGREC|CMDEN_GPLAY|CMDEN_NGPLAY);
    mf->cmd_enable |= CMDEN_GREC;
    mf->enable_menus();
}

void systemStopGameRecording()
{
    if(!game_recording)
	return;
    if(game_file.Write(&game_frame, sizeof(game_frame)) != sizeof(game_frame) ||
       game_file.Write(&game_joypad, sizeof(game_joypad)) != sizeof(game_joypad) ||
       !game_file.Close())
	wxLogError(_("Error writing game recording"));
    game_recording = false;
    MainFrame *mf = wxGetApp().frame;
    mf->cmd_enable &= ~CMDEN_GREC;
    mf->cmd_enable |= CMDEN_NGREC|CMDEN_NGPLAY;
    mf->enable_menus();
}

u32 game_next_frame, game_next_joypad;

void systemStartGamePlayback(const wxString &fname)
{
    GameArea *panel = wxGetApp().frame->GetPanel();
    if(!panel || panel->game_type() == IMAGE_UNKNOWN ||
       !panel->emusys->emuReadState) {
	wxLogError(_("No game in progress to record"));
	return;
    }
    if(game_recording) {
	wxLogError(_("Cannot play game recording while recording"));
	return;
    }
    systemStopGamePlayback();
    wxString fn = fname;
    if(fn.size() < 4 || !wxString(fn.substr(fn.size() - 4)).IsSameAs(wxT(".vmv"), false))
	fn.append(wxT(".vmv"));
    u32 version;
    if(!game_file.Open(fn.c_str(), wxT("rb")) ||
       game_file.Read(&version, sizeof(version)) != sizeof(version) ||
       wxUINT32_SWAP_ON_BE(version) != 1) {
	wxLogError(_("Cannot open recording file %s"), fname.c_str());
	return;
    }
    u32 gf, jp;
    if(game_file.Read(&gf, sizeof(gf)) != sizeof(gf) ||
       game_file.Read(&jp, sizeof(jp)) != sizeof(jp)) {
	wxLogError(_("Error reading game recording"));
	game_file.Close();
	return;
    }
    game_next_frame = wxUINT32_SWAP_ON_BE(gf);
    game_next_joypad = wxUINT32_SWAP_ON_BE(jp);
    fn[fn.size() - 1] = wxT('0');
    if(!panel->emusys->emuReadState(fn.mb_fn_str())) {
	wxLogError(_("Error reading game recording"));
	game_file.Close();
	return;
    }
    game_frame = 0;
    game_joypad = 0;
    game_playback = true;
    MainFrame *mf = wxGetApp().frame;
    mf->cmd_enable &= ~(CMDEN_NGREC|CMDEN_GREC|CMDEN_NGPLAY);
    mf->cmd_enable |= CMDEN_GPLAY;
    mf->enable_menus();
}

void systemStopGamePlayback()
{
    if(!game_playback)
	return;
    game_file.Close();
    game_playback = false;
    MainFrame *mf = wxGetApp().frame;
    mf->cmd_enable &= ~CMDEN_GPLAY;
    mf->cmd_enable |= CMDEN_NGREC|CMDEN_NGPLAY;
    mf->enable_menus();
}



// updates the joystick data (done in background using wxSDLJoy)
bool systemReadJoypads()
{
    return true;
}

// return information about the given joystick, -1 for default joystick
u32 systemReadJoypad(int joy)
{
    if(joy < 0 || joy > 3)
	joy = gopts.default_stick - 1;
    uint32_t ret = joypress[joy];

    if(turbo)
	ret |= KEYM_SPEED;

    u32 af = autofire;
    if(ret & KEYM_AUTO_A) {
	ret |= KEYM_A;
	af |= KEYM_A;
    }
    if(ret & KEYM_AUTO_B) {
	ret |= KEYM_B;
	af |= KEYM_B;
    }
    static int autofire_trigger = 1;
    static bool autofire_state = true;
    u32 af_but = af & ret;
    if(af_but) {
	if(!autofire_state)
	    ret &= ~af_but;
	if(!--autofire_trigger) {
	    autofire_trigger = gopts.autofire_rate;
	    autofire_state = !autofire_state;
	}
    } else {
	autofire_state = true;
	autofire_trigger = gopts.autofire_rate;
    }

    // disallow opposite directionals simultaneously
    ret &= ~((ret & (KEYM_LEFT|KEYM_DOWN|KEYM_MOTION_DOWN|KEYM_MOTION_RIGHT)) >> 1);

    ret &= REALKEY_MASK;

    if(game_recording) {
	u32 rret = ret & ~(KEYM_SPEED|KEYM_CAPTURE);
	if(rret != game_joypad) {
	    game_joypad = rret;
	    u32 gf = wxUINT32_SWAP_ON_BE(game_frame);
	    u32 jp = wxUINT32_SWAP_ON_BE(game_joypad);
	    if(game_file.Write(&gf, sizeof(gf)) != sizeof(gf) ||
	       game_file.Write(&jp, sizeof(jp)) != sizeof(jp)) {
		game_file.Close();
		game_recording = false;
		wxLogError(_("Error writing game recording"));
	    }
	}
    } else if(game_playback) {
	while(game_frame >= game_next_frame) {
	    game_joypad = game_next_joypad;
	    u32 gf, jp;
	    if(game_file.Read(&gf, sizeof(gf)) != sizeof(gf) ||
	       game_file.Read(&jp, sizeof(jp)) != sizeof(jp)) {
		game_file.Close();
		game_playback = false;
		wxString msg(_("Playback ended"));
		systemScreenMessage(msg);
		break;
	    }
	    game_next_frame = wxUINT32_SWAP_ON_BE(gf);
	    game_next_joypad = wxUINT32_SWAP_ON_BE(jp);
	}
	ret = game_joypad;
    }

    return ret;
}

void systemShowSpeed(int speed)
{
    MainFrame *f = wxGetApp().frame;
    wxString s;
    s.Printf(_("%d%%(%d, %d fps)"), speed, systemFrameSkip, frames * speed / 100);
    switch(gopts.osd_speed) {
      case SS_NONE:
	f->GetPanel()->osdstat.clear();
	break;
      case SS_PERCENT:
	f->GetPanel()->osdstat.Printf(_("%d%%"), speed);
	break;
      case SS_DETAILED:
	f->GetPanel()->osdstat = s;
	break;
    }
    wxGetApp().frame->SetStatusText(s, 1);
    frames = 0;
}

int systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;

void system10Frames(int rate)
{
    GameArea *panel = wxGetApp().frame->GetPanel();
    int fs = panel->game_type() == IMAGE_GB ? gopts.gb_frameskip : gopts.gba_frameskip;
    if(fs < 0) {
	// I don't know why this algorithm isn't in common somewhere
	// as is, I copied it from SDL
	static u32 prevclock = 0;
	static int speedadj = 0;
	u32 t = systemGetClock();
	if(!panel->was_paused && prevclock && (t - prevclock) != 10000/rate) {
	    int speed = t == prevclock ? 100 * 10000/rate - (t - prevclock) : 100;
	    // why 98??
	    if(speed >= 98)
		speedadj++;
	    else if(speed < 80)
		speedadj -= (90 - speed) / 5;
	    else
		speedadj--;
	    if(speedadj >= 3) {
		speedadj = 0;
		if(systemFrameSkip > 0)
		    systemFrameSkip--;
	    } else if(speedadj <= -2) {
		speedadj += 2;
		if(systemFrameSkip < 9)
		    systemFrameSkip++;
	    }
	}
	prevclock = t;
	panel->was_paused = false;
    }

    if(gopts.rewind_interval) {
	if(!panel->rewind_time)
	    panel->rewind_time = gopts.rewind_interval * 6;
	else if(!--panel->rewind_time)
	    panel->do_rewind = true;
    }

    if(--systemSaveUpdateCounter == SYSTEM_SAVE_NOT_UPDATED)
	panel->SaveBattery();
    else if(systemSaveUpdateCounter < SYSTEM_SAVE_NOT_UPDATED)
	systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;
}

void systemFrame()
{
    if(game_recording || game_playback)
	game_frame++;
}

// technically, num is ignored in favor of finding the first
// available slot
void systemScreenCapture(int num)
{
    GameArea *panel = wxGetApp().frame->GetPanel();
    wxFileName fn;

    if(!gopts.scrshot_dir.size())
	fn = wxFileName(panel->game_dir(), wxEmptyString);
    else {
	fn = wxFileName(gopts.scrshot_dir, wxEmptyString);
	if(!fn.IsAbsolute())
	    fn = wxFileName(panel->game_dir() + wxT('/') +
			    gopts.scrshot_dir, wxEmptyString);
    }
    do {
	wxString bfn;
	bfn.Printf(wxT("%s%02d"), panel->game_name().c_str(),
		   num++);
	if(gopts.cap_format == 0)
	    bfn.append(wxT(".png"));
	else // if(gopts.cap_format == 1)
	    bfn.append(wxT(".bmp"));
	fn.SetFullName(bfn);
    } while(fn.FileExists());

    fn.Mkdir(0777, wxPATH_MKDIR_FULL);

    if(gopts.cap_format == 0)
	panel->emusys->emuWritePNG(fn.GetFullPath().mb_fn_str());
    else // if(gopts.cap_format == 1)
	panel->emusys->emuWriteBMP(fn.GetFullPath().mb_fn_str());
    wxString msg;
    msg.Printf(_("Wrote snapshot %s"), fn.GetFullPath().c_str());
    systemScreenMessage(msg);
}

u32 systemGetClock()
{
    return wxGetApp().timer.Time();
}

void systemUpdateMotionSensor()
{
    for(int i = 0; i < 4; i++) {
	if(!sensorx[i])
	    sensorx[i] = 2047;
	if(!sensory[i])
	    sensory[i] = 2047;
	if(joypress[i] & KEYM_MOTION_LEFT) {
	    sensorx[i] += 3;
	    if(sensorx[i] > 2197)
		sensorx[i] = 2197;
	    if(sensorx[i] < 2047)
		sensorx[i] = 2057;
	} else if(joypress[i] & KEYM_MOTION_RIGHT) {
	    sensorx[i] -= 3;
	    if(sensorx[i] < 1897)
		sensorx[i] = 1897;
	    if(sensorx[i] > 2047)
		sensorx[i] = 2037;
	} else if(sensorx[i] > 2047) {
	    sensorx[i] -= 2;
	    if(sensorx[i] < 2047)
		sensorx[i] = 2047;
	} else {
	    sensorx[i] += 2;
	    if(sensorx[i] > 2047)
		sensorx[i] = 2047;
	}

	if(joypress[i] & KEYM_MOTION_UP) {
	    sensory[i] += 3;
	    if(sensory[i] > 2197)
		sensory[i] = 2197;
	    if(sensory[i] < 2047)
		sensory[i] = 2057;
	} else if(joypress[i] & KEYM_MOTION_DOWN) {
	    sensory[i] -= 3;
	    if(sensory[i] < 1897)
		sensory[i] = 1897;
	    if(sensory[i] > 2047)
		sensory[i] = 2037;
	} else if(sensory[i] > 2047) {
	    sensory[i] -= 2;
	    if(sensory[i] < 2047)
		sensory[i] = 2047;
	} else {
	    sensory[i] += 2;
	    if(sensory[i] > 2047)
		sensory[i] = 2047;
	}
    }
}

int  systemGetSensorX()
{
    return sensorx[gopts.default_stick - 1];
}

int  systemGetSensorY()
{
    return sensory[gopts.default_stick - 1];
}

class PrintDialog : public wxEvtHandler, public wxPrintout
{
public:
    PrintDialog(const u16 *data, int lines, bool cont);
    ~PrintDialog();
    int ShowModal() { return wxGetApp().frame->ShowModal(dlg); }
private:
    void DoSave(wxCommandEvent&);
    void DoPrint(wxCommandEvent&);
    void ChangeMag(wxCommandEvent&);
    void ShowImg(wxPaintEvent&);
    bool OnPrintPage(int pno);
    void OnPreparePrinting();
    bool HasPage(int pno) { return pno <= npw * nph; }
    void GetPageInfo(int *minp, int *maxp, int *pfrom, int *pto) {
	*minp = 1; *maxp = npw * nph; *pfrom = 1; *pto = 1;
    }

    wxDialog *dlg;
    wxPanel *p;
    wxImage img;
    wxBitmap *bmp;
    wxControlWithItems *mag;

    static wxPrintData *printdata;
    static wxPageSetupDialogData *pagedata;
    wxRect margins;
    int npw, nph;

    DECLARE_CLASS()
};

IMPLEMENT_CLASS(PrintDialog, wxEvtHandler)

PrintDialog::PrintDialog(const u16 *data, int lines, bool cont) :
    img(160, lines), npw(1), nph(1),
    wxPrintout(wxGetApp().frame->GetPanel()->game_name() + wxT(" Printout"))
{
    dlg = wxStaticCast(wxGetApp().frame->FindWindow(XRCID("GBPrinter")), wxDialog);
    p = XRCCTRL(*dlg, "Preview", wxPanel);
    wxScrolledWindow *pp = wxStaticCast(p->GetParent(), wxScrolledWindow);
    wxSize sz(320, lines * 2);
    p->SetSize(sz);
    pp->SetVirtualSize(sz);
    if(lines > 144)
	sz.SetHeight(144 * 2);
    pp->SetSizeHints(sz, sz); // keep sizer from messing with size
    pp->SetSize(sz);
    pp->SetScrollRate(1, 1);
    // why is this so difficult?
    // I want the display area to be 320x288, not the area w/ scrollbars
    wxSize csz = pp->GetClientSize();
    sz += sz - csz;
    pp->SetSizeHints(sz, sz); // keep sizer from messing with size
    pp->SetSize(sz);
    dlg->Fit();
    // what a waste.  wxImage is brain-dead rgb24
    data += 162; // top border
    for(int y = 0; y < lines; y++) {
	for(int x = 0; x < 160; x++) {
	    u16 d = *data++;
	    img.SetRGB(x, y, ((d >> 10) & 0x1f) << 3, ((d >> 5) & 0x1f) << 3,
		       (d & 0x1f) << 3);
	}
	data += 2; // rhs border
    }
    bmp = new wxBitmap(img.Scale(320, 2 * lines, wxIMAGE_QUALITY_HIGH));

    p->Connect(wxEVT_PAINT, wxPaintEventHandler(PrintDialog::ShowImg),
	       NULL, this);
    dlg->Connect(wxID_SAVE, wxEVT_COMMAND_BUTTON_CLICKED,
		 wxCommandEventHandler(PrintDialog::DoSave), NULL, this);
    dlg->Connect(wxID_PRINT, wxEVT_COMMAND_BUTTON_CLICKED,
		 wxCommandEventHandler(PrintDialog::DoPrint), NULL, this);
    dlg->Connect(XRCID("Magnification"), wxEVT_COMMAND_CHOICE_SELECTED,
		 wxCommandEventHandler(PrintDialog::ChangeMag), NULL, this);

    mag = XRCCTRL(*dlg, "Magnification", wxControlWithItems);
    mag->SetSelection(1);
    wxWindow *w = dlg->FindWindow(cont ? wxID_OK : wxID_SAVE);
    if(w)
	w->SetFocus();
    wxButton *cb = wxStaticCast(dlg->FindWindow(wxID_CANCEL), wxButton);
    if(cb)
	cb->SetLabel(_("&Discard"));
}

PrintDialog::~PrintDialog()
{
    p->Disconnect(wxID_ANY, wxEVT_PAINT);
    dlg->Disconnect(wxID_PRINT);
    dlg->Disconnect(wxID_SAVE);
    dlg->Disconnect(XRCID("Magnification"));
    delete bmp;
}

void PrintDialog::ShowImg(wxPaintEvent &evt)
{
    wxPaintDC dc(wxStaticCast(evt.GetEventObject(), wxWindow));
    dc.DrawBitmap(*bmp, 0, 0);
}


void PrintDialog::ChangeMag(wxCommandEvent &evt)
{
    int m = mag->GetSelection() + 1;

    wxScrolledWindow *pp = wxStaticCast(p->GetParent(), wxScrolledWindow);
    wxSize sz(m * 160, m * img.GetHeight());
    delete bmp;
    bmp = new wxBitmap(img.Scale(sz.GetWidth(), sz.GetHeight(), wxIMAGE_QUALITY_HIGH));
    p->SetSize(sz);
    pp->SetVirtualSize(sz);
//    pp->Refresh();
}

static wxString prsav_path;
void PrintDialog::DoSave(wxCommandEvent&)
{
    wxString pats = _("Image files (*.bmp;*.jpg;*.png)|*.bmp;*.jpg;*.png|");
    pats.append(wxALL_FILES);
    wxString dn = wxGetApp().frame->GetPanel()->game_name();
    if(gopts.cap_format == 0)
	dn.append(wxT(".png"));
    else // if(gopts.cap_format == 1)
	dn.append(wxT(".bmp"));
    wxFileDialog fdlg(dlg, _("Save printer image to"), prsav_path, dn,
		      pats, wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
    int ret = fdlg.ShowModal();
    prsav_path = fdlg.GetDirectory();
    if(ret != wxID_OK)
	return;
    wxString of = fdlg.GetPath();
    int m = mag->GetSelection() + 1;
    wxImage scimg = img.Scale(m*160, m*img.GetHeight(), wxIMAGE_QUALITY_HIGH);
    if(scimg.SaveFile(of)) {
	wxString msg;
	msg.Printf(_("Wrote printer output to %s"), of.c_str());
	systemScreenMessage(msg);
	wxButton *cb = wxStaticCast(dlg->FindWindow(wxID_CANCEL), wxButton);
	if(cb) {
	    cb->SetLabel(_("&Close"));
	    cb->SetFocus();
	}
    }
}

wxPrintData *PrintDialog::printdata = NULL;
wxPageSetupDialogData *PrintDialog::pagedata = NULL;

void PrintDialog::OnPreparePrinting()
{
    margins = GetLogicalPageMarginsRect(*pagedata);
    // strange... y is always negative
    margins.y = -margins.y;
    int w = bmp->GetWidth();
    int h = bmp->GetHeight();
    npw = (w + margins.width - 1) / margins.width;
    nph = (h + margins.height - 1) / margins.height;
}

bool PrintDialog::OnPrintPage(int pno)
{
    wxDC *dc = GetDC();
    dc->SetClippingRegion(margins);
    int xoff = (pno - 1) % npw;
    int yoff = (pno - 1) / npw;
    xoff *= margins.width;
    yoff *= margins.height;
    dc->DrawBitmap(*bmp, margins.x - xoff, margins.y - yoff);
}

void PrintDialog::DoPrint(wxCommandEvent&)
{
    if(!printdata)
	printdata = new wxPrintData;
    if(!pagedata)
	pagedata = new wxPageSetupDialogData(*printdata);
    // wxGTK-2.8.12/gnome-2.18.8: can't set margins in page setup
    // wxMAC: need to pop up 2 dialogs to get margins
    // why even bother using standard dialogs, then?
    // well, maybe under msw, where generic dialog may not even exist...
#ifndef __WXMSW__
    wxGenericPageSetupDialog psdlg2(dlg, pagedata);
    psdlg2.ShowModal();
    *printdata = psdlg2.GetPageSetupDialogData().GetPrintData();
    *pagedata = psdlg2.GetPageSetupDialogData();
#else
    wxPageSetupDialog psdlg(dlg, pagedata);
    psdlg.ShowModal();
    *printdata = psdlg.GetPageSetupDialogData().GetPrintData();
    *pagedata = psdlg.GetPageSetupDialogData();
#ifdef __WXMAC__
    // FIXME: is this necessary?  useful? functional?
    wxMacPageMarginsDialog pmdlg(dlg, pagedata);
    pmdlg.ShowModal();
    *printdata = pmdlg.GetPageSetupDialogData().GetPrintData();
    *pagedata = pmdlg.GetPageSetupDialogData();
#endif
#endif

    wxPrintDialogData prdlg(*printdata);
    wxPrinter printer(&prdlg);
    if(printer.Print(dlg, this)) {
	wxString msg = _("Printed");
	systemScreenMessage(msg);
	wxButton *cb = wxStaticCast(dlg->FindWindow(wxID_CANCEL), wxButton);
	if(cb) {
	    cb->SetLabel(_("&Close"));
	    cb->SetFocus();
	}
	*printdata = printer.GetPrintDialogData().GetPrintData();
    }
}

void systemGbPrint(u8 *data,int len, int pages,int feed,int pal,int cont)
{
    ModalPause mp; // this might take a while, so signal a pause
    GameArea *panel = wxGetApp().frame->GetPanel();
    static u16 *accum_prdata;
    static int accum_prdata_len = 0, accum_prdata_size = 0;
    static u16 prdata[162*145] = {0};
    static int picno = 0;
    int lines = len / 40;
    u16 *out = prdata + 162; // 1-pix top border
    for(int y = 0; y < lines / 8; y++) {
	for(int x = 0; x < 160/8; x++) {
	    for(int k = 0; k < 8; k++) {
		int a = *data++;
		int b = *data++;
		for(int j = 0, mask = 0x80; j < 8; j++, mask >>= 1) {
		    int c = ((a & mask) | ((b & mask) << 1)) >> (7 - j);
		    // the guide I read said 0 is on top, but if I do that,
		    // output seems reversed.  So either 11 in pal means
		    // white, or 00 in c means bits 0-1 in pal.
		    // the guide I read also said 00 is white and 11 is black
		    // I'm going with "00 in c means bits 0-1 in pal"
		    if(pal != 0xe4) // 11 10 01 00
			c = (pal & (3 << c*2)) >> c*2;
		    out[j + k * 162] = systemGbPalette[c];
		}
	    }
	    out += 8;
	}
	out += 2 + 7 * 162; // 2-pix rhs border
    }
    // assume no bottom margin means "more coming"
    // probably ought to make this time out somehow
    // or at the very least dump when the game state changes
    u16 *to_print = prdata;
    if((gopts.print_auto_page && !(feed & 15)) || accum_prdata_len) {
	if(!accum_prdata_len)
	    accum_prdata_len = 162; // top border
	accum_prdata_len += lines * 162;
	if(accum_prdata_size < accum_prdata_len) {
	    if(!accum_prdata_size)
		accum_prdata = (u16 *)calloc(accum_prdata_len, 2);
	    else
		accum_prdata = (u16 *)realloc(accum_prdata, accum_prdata_len * 2);
	    accum_prdata_size = accum_prdata_len;
	}
	memcpy(accum_prdata + accum_prdata_len - lines * 162, prdata + 162,
	       lines * 162 * 2);
	if(gopts.print_auto_page && !(feed & 15))
	    return;
	to_print = accum_prdata;
	lines = accum_prdata_len / 162 - 1;
	accum_prdata_len = 0;
    }
    if(gopts.print_screen_cap) {
	wxFileName fn;

	if(!gopts.scrshot_dir.size())
	    fn = wxFileName(panel->game_dir(), wxEmptyString);
	else {
	    fn = wxFileName(gopts.scrshot_dir, wxEmptyString);
	    if(!fn.IsAbsolute())
		fn = wxFileName(panel->game_dir() + wxT('/') +
				gopts.scrshot_dir, wxEmptyString);
	}
	int num = 1;
	do {
	    wxString bfn;
	    bfn.Printf(wxT("%s-print%02d"), panel->game_name().c_str(),
		       num++);
	    if(gopts.cap_format == 0)
		bfn.append(wxT(".png"));
	    else // if(gopts.cap_format == 1)
		bfn.append(wxT(".bmp"));
	    fn.SetFullName(bfn);
	} while(fn.FileExists());

	fn.Mkdir(0777, wxPATH_MKDIR_FULL);

	int d = systemColorDepth;
	int rs = systemRedShift, bs = systemBlueShift, gs = systemGreenShift;
	systemColorDepth = 16;
	systemRedShift = 10;
	systemGreenShift = 5;
	systemBlueShift = 0;
	wxString of = fn.GetFullPath();
	bool ret = gopts.cap_format == 0 ?
	    utilWritePNGFile(of.mb_fn_str(), 160, lines, (u8 *)to_print) :
	    utilWriteBMPFile(of.mb_fn_str(), 160, lines, (u8 *)to_print);
	if(ret) {
	    wxString msg;
	    msg.Printf(_("Wrote printer output to %s"), of.c_str());
	    systemScreenMessage(msg);
	}
	systemColorDepth = d;
	systemRedShift = rs;
	systemGreenShift = gs;
	systemBlueShift = bs;
	return;
    }
    PrintDialog dlg(to_print, lines, !(feed & 15));
    int ret = dlg.ShowModal();
    if(ret == wxID_OK) {
	accum_prdata_len = (lines + 1) * 162;
	if(to_print != accum_prdata) {
	    if(accum_prdata_size < accum_prdata_len) {
		if(!accum_prdata_size)
		    accum_prdata = (u16 *)calloc(accum_prdata_len, 2);
		else
		    accum_prdata = (u16 *)realloc(accum_prdata, accum_prdata_len * 2);
		accum_prdata_size = accum_prdata_len;
	    }
	    memcpy(accum_prdata, to_print, accum_prdata_len * 2);
	}
    }
}

void systemScreenMessage(const wxString &msg)
{
    wxPuts(msg);
    MainFrame *f = wxGetApp().frame;
    GameArea *panel = f->GetPanel();
    if(!panel->osdtext.empty())
	f->PopStatusText();
    f->PushStatusText(msg);
    panel->osdtext = msg;
    panel->osdtime = systemGetClock();
}

void systemScreenMessage(const char *msg)
{
    systemScreenMessage(wxString(msg, wxConvLibc));
}

bool systemCanChangeSoundQuality()
{
#ifndef NO_FFMPEG
    GameArea *panel = wxGetApp().frame->GetPanel();
    if(panel)
	return !panel->IsRecording();
#endif
    return true;
}

bool systemPauseOnFrame()
{
    if(pause_next) {
	pause_next = false;
	MainFrame *fr = wxGetApp().frame;
	fr->GetPanel()->Pause();
	return true;
    }
    return false;
}

void systemGbBorderOn()
{
    GameArea *panel = wxGetApp().frame->GetPanel();
    if(panel)
	panel->AddBorder();
}

class SoundDriver;
SoundDriver * systemSoundInit()
{
    soundShutdown();
    switch(gopts.audio_api) {
    case AUD_SDL:
	return new SoundSDL();
#ifndef NO_OAL
    case AUD_OPENAL:
	return newOpenAL();
#endif
#ifdef __WXMSW__
    case AUD_DIRECTSOUND:
	return newDirectSound();
#ifndef NO_XAUDIO2
    case AUD_XAUDIO2:
	return newXAudio2_Output();
#endif
#endif
    default:
	gopts.audio_api = 0;
    }
}

void systemOnWriteDataToSoundBuffer(const u16 * finalWave, int length)
{
#ifndef NO_FFMPEG
    GameArea *panel = wxGetApp().frame->GetPanel();
    if(panel)
	panel->AddFrame(finalWave, length);
#endif
}

void systemOnSoundShutdown()
{
}

extern int (*remoteSendFnc)(char *, int);
extern int (*remoteRecvFnc)(char *, int);
extern void (*remoteCleanUpFnc)();

#ifndef __WXMSW__
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/poll.h>

static wxString pty_slave;
static int pty_master = -1;

static int debugReadPty(char *buf, int len)
{
    while(1) {
	struct pollfd fd;
	fd.fd = pty_master;
	fd.events = POLLIN;
	if(poll(&fd, 1, 200) != 0)
	    return read(pty_master, buf, len);
	else
	    return -2; // try to let repaints & such run
    }
}

static int debugWritePty(/* const */char *buf, int len)
{
    return write(pty_master, buf, len);
}

static void debugClosePty()
{
    if(pty_master >= 0) {
	close(pty_master);
	pty_master = -1;
    }
}

bool debugOpenPty()
{
    if(pty_master >= 0) // should never happen
	close(pty_master);
    const char *slave_name;
    if((pty_master = posix_openpt(O_RDWR|O_NOCTTY)) < 0 ||
       grantpt(pty_master) < 0 || unlockpt(pty_master) < 0 ||
       !(slave_name = ptsname(pty_master))) {
	wxLogError(_("Error opening pseudo tty: %s"), wxString(strerror(errno),
							       wxConvLibc).c_str());
	if(pty_master >= 0) {
	    close(pty_master);
	    pty_master = -1;
	}
	return false;
    }
    pty_slave = wxString(slave_name, wxConvLibc);
    remoteSendFnc = debugWritePty;
    remoteRecvFnc = debugReadPty;
    remoteCleanUpFnc = debugClosePty;
    return true;
}

const wxString &debugGetSlavePty()
{
    return pty_slave;
}

bool debugWaitPty()
{
    if(pty_master < 0)
	return false;
    struct pollfd fd;
    fd.fd = pty_master;
    fd.events = POLLIN;
    return poll(&fd, 1, 100) > 0;
}
#endif

#include <wx/socket.h>

static wxSocketServer *debug_server = NULL;
wxSocketBase *debug_remote = NULL;

static int debugReadSock(char *buf, int len)
{
    debug_remote->SetFlags(wxSOCKET_BLOCK);
    MainFrame *f = wxGetApp().frame;
    // WaitForRead does not obey wxSOCKET_BLOCK
    // It's hard to prevent menu command selection while GUI is active
    // so this will likely fail if the user isn't careful
    // the only fix in place is to prevent recursive idle loop
    f->StartModal();
    bool done = debug_remote->WaitForRead(0, 200);
    f->StopModal();
    if(!done)
	return -2;
    debug_remote->Read(buf, len);
    if(debug_remote->Error())
	return -1;
    return debug_remote->LastCount();
}

static int debugWriteSock(char *buf, int len)
{
    debug_remote->SetFlags(wxSOCKET_WAITALL|wxSOCKET_BLOCK);
    debug_remote->Write(buf, len);
    if(debug_remote->Error())
	return -1;
    return debug_remote->LastCount();
}

static void debugCloseSock()
{
    delete debug_remote;
    debug_remote = NULL;
    delete debug_server;
    debug_server = NULL;
}

bool debugStartListen(int port)
{
    delete debug_server; // should never be necessary
    delete debug_remote;
    debug_remote = NULL;
    wxIPV4address addr;
    addr.Service(port);
    addr.AnyAddress(); // probably ought to have a flag to select any/localhost
    debug_server = new wxSocketServer(addr, wxSOCKET_REUSEADDR);
    remoteSendFnc = debugWriteSock;
    remoteRecvFnc = debugReadSock;
    remoteCleanUpFnc = debugCloseSock;
    if(debug_server->IsOk())
	return true;
    wxLogError(_("Error setting up server socket (%d)"), debug_server->LastError());
    return false;
}

bool debugWaitSocket()
{
    if(!debug_server)
	return false;
    if(debug_remote)
	return true;
    debug_server->WaitForAccept(0, 100);
    debug_remote = debug_server->Accept(false);
    return debug_remote != NULL;
}

static void debuggerOutput(const char *s, u32 addr)
{
}

void debuggerSignal(int sig, int number)
{
}

void (*dbgMain)();
void debuggerMain()
{
}

bool debugger;

void (*dbgOutput)(const char *s, u32 addr) = debuggerOutput;
void (*dbgSignal)(int sig,int number) = debuggerSignal;

void debuggerBreakOnWrite(u32 address, u32 oldvalue, u32 value, int size, int t)
{
}

void log(const char *defaultMsg, ...)
{
    static FILE *out = NULL;

    if(out == NULL) {
	// FIXME: this should be an option
	out = fopen("trace.log","w");
	if(!out)
	    return;
    }

    va_list valist;

    va_start(valist, defaultMsg);
    vfprintf(out, defaultMsg, valist);
    va_end(valist);
    char buf[2048];
    va_start(valist, defaultMsg);
    vsnprintf(buf, 2048, defaultMsg, valist);
    va_end(valist);
    wxGetApp().log.append(wxString(buf, wxConvLibc));
    LogDialog *d = wxGetApp().frame->logdlg;
    if(d)
	d->Update();
}
